/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/* 
 * Copyright (c) 2009 City University of Hong Kong
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License m_version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author: Wilson Thong (wilsonwk@ee.cityu.edu.hk) 
 */
#include "unicast-session-monitor.h"
#include "ns3/packet-traversed-hop-calculator.h"
#include "ns3/callback.h"
#include "ns3/ipv4.h"

using namespace ns3::eecityu;
using namespace ns3;
using namespace std;

namespace ns3 {
namespace eecityu {

NS_LOG_COMPONENT_DEFINE ("UnicastSessionMonitor");
NS_OBJECT_ENSURE_REGISTERED (UnicastSessionMonitor);

uint32_t UnicastSessionMonitor::g_indexCounter = 0;

TypeId
UnicastSessionMonitor::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::eecityu::UnicastSessionMonitor")
    .SetParent<Object> ()
    .AddConstructor<UnicastSessionMonitor> ()
    .AddAttribute ("SourceApplication", 
                   "The traffic soucing application of the unicast traffic session.",
                   PointerValue (),
                   MakePointerAccessor (&UnicastSessionMonitor::GetSourceApplication,
                                        &UnicastSessionMonitor::SetSourceApplication),
                   MakePointerChecker<Application> ())
    .AddAttribute ("SinkApplication", 
                   "The traffic sinking application of the unicast traffic session.",
                   PointerValue (),
                   MakePointerAccessor (&UnicastSessionMonitor::GetSinkApplication,
                                        &UnicastSessionMonitor::SetSinkApplication),
                   MakePointerChecker<Application> ())
    .AddAttribute ("PacketReorderCalculator", 
                   "The calculator to count different packet statistics, such as number of "
                   "duplicated packets, number of reordered packets, etc",
                   PointerValue (),
                   MakePointerAccessor (&UnicastSessionMonitor::m_reorderMeasure),
                   MakePointerChecker<PacketReorderCalculator> ())
    .AddAttribute ("PacketTraversedHopCalculator", 
                   "The calculator for obtaining traversed hops of received packets",
                   PointerValue (),
                   MakePointerAccessor (&UnicastSessionMonitor::m_traversedHopMeasure),
                   MakePointerChecker<PacketTraversedHopCalculator> ())
    ;

  return tid;
}

UnicastSessionMonitor::UnicastSessionMonitor(void)
: m_sourceApplication (0), m_sinkApplication (0), m_index (g_indexCounter++)
{
  m_reorderMeasure = CreateObject<PacketReorderCalculator> ();
  m_traversedHopMeasure = CreateObject<PacketTraversedHopCalculator> ();
  NS_ASSERT_MSG (g_indexCounter > 0, "Global index counter g_indexCounter wrapped around to zero! "
    "Too many unicast session monitors constructed!");
}

uint32_t 
UnicastSessionMonitor::GetNArrival () const
{
  return m_reorderMeasure->GetNArrival ();
}

uint32_t 
UnicastSessionMonitor::GetNDuplicated () const
{
  return m_reorderMeasure->GetNDuplicated ();
}

uint32_t 
UnicastSessionMonitor::GetNMissing () const
{
  return m_reorderMeasure->GetNMissing ();
}

uint32_t 
UnicastSessionMonitor::GetNReordered () const
{
  return m_reorderMeasure->GetNReordered ();
}

uint32_t 
UnicastSessionMonitor::GetNPacketsSent () const
{
  return m_reorderMeasure->GetNPacketsSent ();
}

uint64_t 
UnicastSessionMonitor::GetNBytesSent () const
{
  return m_reorderMeasure->GetNBytesSent ();
}

uint64_t 
UnicastSessionMonitor::GetReorderingExtent (uint64_t s) const
{
  return m_reorderMeasure->GetReorderingExtent (s);
}

Time 
UnicastSessionMonitor::GetLateTime (uint64_t s) const
{
  return m_reorderMeasure->GetLateTime (s);
}

uint32_t 
UnicastSessionMonitor::GetPathDistance () const
{
  NS_LOG_FUNCTION_NOARGS ();

  PathDetailCalculator calc;
  calc.Calculate (m_sourceApplication->GetNode (), m_sinkApplication->GetNode ());
  return calc.GetCalculatedHopDistance ();
}

uint32_t 
UnicastSessionMonitor::GetSessionIndex () const
{
  NS_LOG_FUNCTION_NOARGS ();

  return m_index;
}

bool 
UnicastSessionMonitor::IsMissing (uint64_t s) const
{
  return m_reorderMeasure->IsMissing (s);
}

Ptr<Node> 
UnicastSessionMonitor::GetSource (void) const
{
  NS_LOG_FUNCTION_NOARGS ();

  NS_ASSERT_MSG (m_sourceApplication, "Source application not yet set!");
  return m_sourceApplication->GetNode ();
}

Ptr<Node> 
UnicastSessionMonitor::GetDestination (void) const
{
  NS_LOG_FUNCTION_NOARGS ();

  NS_ASSERT_MSG (m_sinkApplication, "Sink application not yet set!");
  return m_sinkApplication->GetNode ();
}

Ptr<Application> 
UnicastSessionMonitor::GetSourceApplication () const
{
  NS_LOG_FUNCTION_NOARGS ();

  return m_sourceApplication;
}

void 
UnicastSessionMonitor::SetSourceApplication (Ptr<Application> app)
{
  NS_LOG_FUNCTION (app);

  NS_ASSERT_MSG (app != 0, "Source application pointer cannot be null!");
  NS_ASSERT_MSG (m_sourceApplication == 0, "Cannot reassign a different source application to this monitor!");

  // init the unicast session monitor itself
  m_sourceApplication = app;
  m_sourceApplication->TraceConnectWithoutContext (
    "Tx", 
    MakeCallback (&UnicastSessionMonitor::HandlePacketSentEvent, this)
    );
  // init the traversed hops calculator
  m_traversedHopMeasure->SetAttribute ("SourceApplication", PointerValue (app));
}

Ptr<Application> 
UnicastSessionMonitor::GetSinkApplication () const
{
  NS_LOG_FUNCTION_NOARGS ();

  return m_sinkApplication;
}

void 
UnicastSessionMonitor::SetSinkApplication (Ptr<Application> app)
{
  NS_LOG_FUNCTION (app);

  NS_ASSERT_MSG (app != 0, "Sink application pointer cannot be null!");
  NS_ASSERT_MSG (m_sinkApplication == 0, "Cannot reassign a different sink application to this monitor!");
  
  // init the unicast session monitor itself
  m_sinkApplication = app;
  m_sinkApplication->TraceConnectWithoutContext (
    "Rx", 
    MakeCallback (&UnicastSessionMonitor::HandlePacketReceivedEvent, this)
    );
  // init the PacketTraversedHopCalculator
  Ptr<Ipv4> dstIpv4 = app->GetNode ()->GetObject<Ipv4> ();
  NS_ASSERT_MSG (dstIpv4, "Ipv4 protocol is not found on the destination node!");
  dstIpv4->TraceConnectWithoutContext (
    "LocalDeliver",
    MakeCallback (&PacketTraversedHopCalculator::RecordRx, m_traversedHopMeasure));

}

void 
UnicastSessionMonitor::DoDispose (void)
{
  NS_LOG_FUNCTION_NOARGS ();

  m_sourceApplication->TraceDisconnectWithoutContext (
    "Tx",
    MakeCallback (&UnicastSessionMonitor::HandlePacketSentEvent, this));
  m_sinkApplication->TraceDisconnectWithoutContext (
    "Rx",
    MakeCallback (&UnicastSessionMonitor::HandlePacketReceivedEvent, this));
  Ptr<Ipv4> dstIpv4 = m_sinkApplication->GetNode ()->GetObject<Ipv4> ();
  NS_ASSERT_MSG (dstIpv4, "Ipv4 protocol is not found on the destination node!");
  dstIpv4->TraceDisconnectWithoutContext (
    "LocalDeliver",
    MakeCallback (&PacketTraversedHopCalculator::RecordRx, m_traversedHopMeasure));

  m_packetRegister.clear ();
  m_sourceApplication = 0;
  m_sinkApplication = 0;
  m_reorderMeasure->Dispose ();
  m_traversedHopMeasure->Dispose ();

  Object::DoDispose ();
}

void 
UnicastSessionMonitor::HandlePacketSentEvent (Ptr<const Packet> p)
{
  NS_LOG_FUNCTION (p->GetUid ());
  NS_LOG_LOGIC ("Source node = " << GetSource ()->GetId () << ", Session = " << m_index);

  // Registering this packet with this session
  m_packetRegister.insert (p->GetUid ());
  // informing the qosMeasure when a packet is about to be sent
  uint64_t seqNum = m_reorderMeasure->PrepareTx (p);
  // registering the packet for calculating its traversed hops
  m_traversedHopMeasure->PrepareTx (p);
  // print out the packet information before it is sent
  NS_LOG_INFO ("Session-" << GetSessionIndex () << ": Source node = " << GetSource ()->GetId () 
    << " just sent the packet = " << p->GetUid ()
    << " with seq no = " << seqNum
    );
}

void 
UnicastSessionMonitor::HandlePacketReceivedEvent (Ptr<const Packet> p, const Address& rawAddr)
{
  NS_LOG_FUNCTION (p->GetUid () << rawAddr);
  NS_LOG_LOGIC ("Destination node = " << GetDestination ()->GetId () << ", Session = " << m_index);

  // Check if the arrival packet was registered with this session
  bool registered = m_packetRegister.find (p->GetUid ()) != m_packetRegister.end ();
  NS_ASSERT_MSG (registered, "This packet-" << p->GetUid () 
    << " was not sent from this session-" << m_index);
  // read and remove the sequence number from the packet
  uint64_t seqNum = m_reorderMeasure->RecordRx (p);
  // convert the raw address into a InetSocketAddress
  InetSocketAddress addr = InetSocketAddress::ConvertFrom (rawAddr);
  NS_LOG_INFO ("Session-" << GetSessionIndex () << ": Destination node = " << GetDestination ()->GetId () 
    << " just received the packet = " << p->GetUid () 
    << " with sequence no = " << seqNum
    << " from " << addr.GetIpv4 () << ":" << addr.GetPort ());
}

} // nacespace eecityu
} // namespace ns3

